#![allow(non_snake_case)]
use std::io::Read;

use super::{Instruction, INDEX_OUT_OF_BOUNDS};

#[derive(Clone, Debug, Default)]
pub struct VM {
    _IP: i32,
    _CS: Box<Vec<(Instruction, i32)>>,
    _SS: Box<Vec<i32>>,
}

impl VM {
    pub fn new(instruction_list: &Vec<(Instruction, i32)>) -> Self {
        Self {
            _IP: 0,
            _CS: Box::new(instruction_list.clone()),
            _SS: Box::new(vec![0, 0]),
        }
    }
    pub fn run(&mut self){
        while self._IP < self._CS.len() as i32 {
            // println!("ip:{}", self._IP);
            let cs_clone = self._CS.clone();
            self.exec_instruction(&cs_clone.get(self._IP as usize).unwrap());
            self._IP += 1;
        }
    }
    fn exec_instruction(&mut self, instruction: &(Instruction, i32)) {
        match instruction.0 {
            Instruction::LDC => {
                self._SS[0] = instruction.1;
            }
            Instruction::LD => {
                let index1 = *self._SS.get(1).unwrap();
                let index0 = *self._SS.get(0).unwrap();
                let index = (index1 - index0).abs();
                self._SS[0] = *self._SS.get(index as usize).unwrap();
            }
            Instruction::ALD => {
                let mut index = *self._SS.get(0).unwrap();
                if index < 0 {index = 0;}
                self._SS[0] = *self._SS.get(index as usize + 2).unwrap();
            }
            Instruction::ST => {
                let ss = self._SS.clone();
                let index = ss.get(1).unwrap() - ss.get(0).unwrap();
                if index >= 0 {
                    self._SS[index as usize] = *self._SS.last().unwrap();
                }
            }
            Instruction::AST => {
                let ss = self._SS.clone();
                let index = ss.get(0).unwrap() + 2;
                if index >= 0 {
                    self._SS[index as usize] = *self._SS.last().unwrap();
                }
            }
            Instruction::PUSH => {
                self._SS.push(*self._SS.get(0).unwrap());
            }
            Instruction::POP => {
                self._SS.pop();
            }
            Instruction::JMP => {
                self._IP += instruction.1 - 1;
            }
            Instruction::JZ => {
                if self._SS.get(0) != Some(&0) {
                    self._IP += instruction.1 - 1;
                }
            }
            Instruction::ADD => {
                self._SS[0] = self._SS.last().unwrap() + self._SS.get(0).unwrap();
            }
            Instruction::SUB => {
                self._SS[0] = self._SS.last().unwrap() - self._SS.get(0).unwrap();
            }
            Instruction::MUL => {
                self._SS[0] = self._SS.last().unwrap() * self._SS.get(0).unwrap();
            }
            Instruction::DIV => {
                self._SS[0] = self._SS.last().unwrap() / self._SS.get(0).unwrap();
            }
            Instruction::LT => {
                self._SS[0] = (self._SS.last().unwrap() < self._SS.get(0).unwrap()) as i32;
            }
            Instruction::LE => {
                self._SS[0] = (self._SS.last().unwrap() <= self._SS.get(0).unwrap()) as i32;
            }
            Instruction::GT => {
                self._SS[0] = (self._SS.last().unwrap() > self._SS.get(0).unwrap()) as i32;
            }
            Instruction::GE => {
                self._SS[0] = (self._SS.last().unwrap() >= self._SS.get(0).unwrap()) as i32;
            }
            Instruction::EQ => {
                self._SS[0] = (self._SS.last().unwrap() == self._SS.get(0).unwrap()) as i32;
            }
            Instruction::NE => {
                self._SS[0] = (self._SS.last().unwrap() != self._SS.get(0).unwrap()) as i32;
            }
            Instruction::IN => {
                let mut tmp = String::new();
                std::io::stdin().read_line(&mut tmp);
                self._SS[0] = tmp.trim().parse().unwrap_or_else(|x| -> i32 {
                    println!("x:{:?}", x);
                    0
                });
            }
            Instruction::OUT => {
                print!("{}", self._SS.get(0).unwrap());
            }
            Instruction::ADDR => {
                self._SS.insert(0, self._SS.len() as i32 - instruction.1);
            }
            Instruction::CALL => {
                self._SS.push(self._SS[1]);
                self._SS[1] = self._SS.len() as i32 - 2;
                self._SS.push(self._IP);
                self._IP += instruction.1 - 1;
            }
            Instruction::RET => {
                self._IP = *self._SS.last().unwrap();
                self._SS.pop();
                self._SS[1] = *self._SS.last().unwrap();
                self._SS.pop();
            }
        }
    }
}

#[test]
fn test() {
    let mut a = "5".to_string();
    println!("a:{}", a);
    // let mut tmp = String::default();
    std::io::stdin().read_line(&mut a);
    println!("a:{}", a);
}
